﻿using System;
using System.Globalization;
using System.IO;
using Hive.Core.Common.Shell;

namespace Hive.Core.Model
{
    /// <summary>
    /// A Subversion source repository.
    /// </summary>
    public class SubversionSourceRepository : ISourceRepository
    {
        private readonly SubversionSourceRepositoryConfiguration configuration;
        private string subversionCommandPath;

        /// <summary>
        /// Creates a Subversion source repository.
        /// </summary>
        /// <param name="configuration">The configuration.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="configuration"/> is null.</exception>
        public SubversionSourceRepository(SubversionSourceRepositoryConfiguration configuration)
        {
            if (configuration == null)
                throw new ArgumentNullException("configuration");

            this.configuration = configuration;
            subversionCommandPath = "svn.exe";
        }

        /// <summary>
        /// Gets or sets the path of the "svn.exe" command.
        /// </summary>
        /// <value>The command path.  Defaults to "svn.exe" which assumes that
        /// Subversion is installed within the system path.</value>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception>
        public string SubversionCommandPath
        {
            get { return subversionCommandPath; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
                subversionCommandPath = value;
            }
        }

        /// <inheritdoc />
        public SourceRepositoryConfiguration Configuration
        {
            get { return configuration; }
        }

        /// <inheritdoc />
        public string DefaultBranchName
        {
            get { return "trunk"; }
        }

        /// <inheritdoc />
        public void CheckoutOrUpdate(string packageName, string branchName, DirectoryInfo destinationDirectory, IShell shell)
        {
            if (packageName == null)
                throw new ArgumentNullException("packageName");
            if (branchName == null)
                throw new ArgumentNullException("branchName");
            if (destinationDirectory == null)
                throw new ArgumentNullException("destinationDirectory");
            if (shell == null)
                throw new ArgumentNullException("shell");

            try
            {
                if (HasSubversionMetadata(destinationDirectory))
                    Update(destinationDirectory, shell);
                else
                    Checkout(packageName, branchName, destinationDirectory, shell);
            }
            catch (SourceRepositoryException)
            {
                throw;
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new SourceRepositoryException(string.Format("Failed to checkout or update source for branch '{0}' of package '{1}'.",
                    branchName, packageName), ex);
            }
        }

        private static bool HasSubversionMetadata(DirectoryInfo destinationDirectory)
        {
            return Directory.Exists(Path.Combine(destinationDirectory.FullName, ".svn"));
        }

        private void Checkout(string packageName, string branchName, DirectoryInfo destinationDirectory, IShell shell)
        {
            string repositoryRoot = configuration.GetRepositoryRoot(packageName, branchName);
            if (repositoryRoot == null)
                throw new SourceRepositoryException(string.Format("There is no mapping for the Subversion repository root for branch '{0}' of package '{1}'.",
                    branchName, packageName));

            if (!destinationDirectory.Exists)
                destinationDirectory.Create();

            var shellCommand = new ShellCommand(subversionCommandPath,
                string.Concat("checkout \"", repositoryRoot, "\" ."),
                destinationDirectory);

            int exitCode = shell.Execute(shellCommand, null, null);
            if (exitCode != 0)
                throw new SourceRepositoryException(
                    string.Format("Checkout failed!  Subversion exited with code {0}.", exitCode));
        }

        private void Update(DirectoryInfo destinationDirectory, IShell shell)
        {
            var shellCommand = new ShellCommand(subversionCommandPath, "update", destinationDirectory);

            int exitCode = shell.Execute(shellCommand, null, null);
            if (exitCode != 0)
                throw new SourceRepositoryException(string.Format("Update failed!  Subversion exited with code {0}.", exitCode));
        }
    }
}
